msg_tool\scripts\artemis\panmimisoft/
txt.rs

1use crate::scripts::base::*;
2use crate::types::*;
3use crate::utils::encoding::*;
4use crate::utils::escape::*;
5use anyhow::Result;
6use std::collections::HashSet;
7use std::ops::{Deref, DerefMut};
8use std::sync::Arc;
9use unicode_segmentation::UnicodeSegmentation;
10
11#[derive(Debug, Clone)]
12/// Artemis TXT script builder
13pub struct TxtBuilder {}
14
15impl TxtBuilder {
16    /// Creates a new instance of `TxtBuilder`
17    pub const fn new() -> Self {
18        Self {}
19    }
20}
21
22impl ScriptBuilder for TxtBuilder {
23    fn default_encoding(&self) -> Encoding {
24        Encoding::Utf8
25    }
26
27    fn build_script(
28        &self,
29        buf: Vec<u8>,
30        _filename: &str,
31        encoding: Encoding,
32        _archive_encoding: Encoding,
33        config: &ExtraConfig,
34        _archive: Option<&Box<dyn Script>>,
35    ) -> Result<Box<dyn Script>> {
36        Ok(Box::new(TxtScript::new(buf, encoding, config)?))
37    }
38
39    fn extensions(&self) -> &'static [&'static str] {
40        &["txt"]
41    }
42
43    fn script_type(&self) -> &'static ScriptType {
44        &ScriptType::ArtemisPanmimisoftTxt
45    }
46}
47
48/// Artemis TXT script nodes
49pub trait Node {
50    /// Serialize the node to a string representation.
51    fn serialize(&self) -> String;
52}
53
54#[derive(Debug, Clone, PartialEq)]
55/// Represents a comment node in Artemis TXT scripts.
56pub struct CommentNode(pub String);
57
58impl Node for CommentNode {
59    fn serialize(&self) -> String {
60        format!("//{}", self.0)
61    }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65/// Empty Line Node
66pub struct EmptyLineNode;
67
68impl Node for EmptyLineNode {
69    fn serialize(&self) -> String {
70        String::new()
71    }
72}
73
74#[derive(Debug, Clone, PartialEq)]
75/// Represents a label node in Artemis TXT scripts.
76pub struct LabelNode(pub String);
77
78impl Node for LabelNode {
79    fn serialize(&self) -> String {
80        format!("*{}", self.0)
81    }
82}
83
84#[derive(Debug, Clone, PartialEq)]
85/// Represents a tag node in Artemis TXT scripts.
86pub struct TagNode {
87    /// The name of the tag.
88    pub name: String,
89    /// The attributes of the tag, represented as a vector of key-value pairs.
90    pub attributes: Vec<(String, Option<String>)>,
91}
92
93impl Node for TagNode {
94    fn serialize(&self) -> String {
95        let attributes = self
96            .attributes
97            .iter()
98            .map(|(key, value)| {
99                if let Some(val) = value {
100                    format!("{}=\"{}\"", key, val)
101                } else {
102                    key.clone()
103                }
104            })
105            .collect::<Vec<_>>()
106            .join(" ");
107        if attributes.is_empty() {
108            format!("[{}]", self.name)
109        } else {
110            format!("[{} {}]", self.name, attributes)
111        }
112    }
113}
114
115impl TagNode {
116    fn ser_attributes_xml(&self) -> String {
117        let mut parts = Vec::new();
118        for (key, value) in self.attributes.iter() {
119            match value {
120                None => {
121                    parts.push(key.clone());
122                }
123                Some(val) => {
124                    parts.push(format!("{}=\"{}\"", key, escape_xml_attr_value(val)));
125                }
126            }
127        }
128        parts.join(" ")
129    }
130
131    /// Get attribute value of attribute in tag by name.
132    pub fn get_attr(&self, attr: &str) -> Option<&str> {
133        self.attributes
134            .iter()
135            .find(|(key, _)| key == attr)
136            .and_then(|(_, value)| value.as_deref())
137    }
138
139    /// Returns true if the tag is not suitable for name.
140    pub fn is_blocked_name(&self, set: &HashSet<String>) -> bool {
141        self.name.is_ascii() || set.contains(&self.name)
142    }
143
144    /// Checks if the tag has a specific attribute.
145    pub fn has_attr(&self, attr: &str) -> bool {
146        self.attributes.iter().any(|(key, _)| key == attr)
147    }
148
149    /// Sets the value of an attribute in the tag.
150    pub fn set_attr(&mut self, attr: &str, value: Option<String>) {
151        if let Some(pos) = self.attributes.iter().position(|(key, _)| key == attr) {
152            self.attributes[pos].1 = value;
153        } else {
154            self.attributes.push((attr.to_string(), value));
155        }
156    }
157
158    /// Converts the node to a xml-like string representation.
159    pub fn to_xml(&self) -> String {
160        let attributes = self.ser_attributes_xml();
161        if attributes.is_empty() {
162            format!("<{}>", self.name)
163        } else {
164            format!("<{} {}>", self.name, attributes)
165        }
166    }
167}
168
169#[derive(Debug, Clone, PartialEq)]
170/// Represents a text node in Artemis TXT scripts.
171pub struct TextNode(pub String);
172
173#[derive(Debug, Clone, PartialEq)]
174/// Represents a node in a TXT line.
175pub enum TxtLineNode {
176    Comment(CommentNode),
177    Tag(TagNode),
178    Text(TextNode),
179}
180
181impl TxtLineNode {
182    /// Checks if the node is a comment.
183    pub fn is_comment(&self) -> bool {
184        matches!(self, TxtLineNode::Comment(_))
185    }
186
187    /// Checks if the node is a tag.
188    ///
189    /// * `tag` - The name of the tag.
190    pub fn is_tag(&self, tag: &str) -> bool {
191        matches!(self, TxtLineNode::Tag(node) if node.name == tag)
192    }
193
194    /// Returns true if the tag is a blocked name.
195    pub fn is_tag_blocked_name(&self, set: &HashSet<String>) -> bool {
196        if let TxtLineNode::Tag(node) = self {
197            node.is_blocked_name(set)
198        } else {
199            false
200        }
201    }
202
203    /// Returns an iterator over the keys of the attributes of the tag node.
204    pub fn tag_attr_keys<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
205        if let TxtLineNode::Tag(node) = self {
206            Box::new(node.attributes.iter().map(|(key, _)| key.as_str()))
207        } else {
208            Box::new(std::iter::empty())
209        }
210    }
211
212    pub fn tag_get_attr<'a>(&'a self, attr: &str) -> Option<&'a str> {
213        if let TxtLineNode::Tag(node) = self {
214            node.get_attr(attr)
215        } else {
216            None
217        }
218    }
219
220    /// Checks if the tag has a specific attribute.
221    pub fn tag_has_attr(&self, attr: &str) -> bool {
222        if let TxtLineNode::Tag(node) = self {
223            node.attributes.iter().any(|(key, _)| key == attr)
224        } else {
225            false
226        }
227    }
228
229    pub fn tag_set_attr(&mut self, attr: &str, value: Option<String>) {
230        if let TxtLineNode::Tag(node) = self {
231            node.set_attr(attr, value);
232        }
233    }
234
235    /// Converts the node to a xml-like string representation.
236    pub fn to_xml(&self) -> String {
237        match self {
238            TxtLineNode::Comment(_) => String::new(), // Ignore comments in XML
239            TxtLineNode::Tag(n) => {
240                if (n.name == "rt2" || n.name == "ret2") && n.attributes.is_empty() {
241                    "\n".to_string()
242                } else {
243                    n.to_xml()
244                }
245            }
246            TxtLineNode::Text(n) => escape_xml_text_value(&n.0),
247        }
248    }
249}
250
251impl Node for TxtLineNode {
252    fn serialize(&self) -> String {
253        match self {
254            TxtLineNode::Comment(node) => node.serialize(),
255            TxtLineNode::Tag(node) => node.serialize(),
256            TxtLineNode::Text(node) => node.0.clone(),
257        }
258    }
259}
260
261#[derive(Debug, Clone, PartialEq)]
262/// Represents a line in Artemis TXT scripts, which can contain multiple nodes.
263pub struct TxtLine(pub Vec<TxtLineNode>);
264
265impl Deref for TxtLine {
266    type Target = Vec<TxtLineNode>;
267
268    fn deref(&self) -> &Self::Target {
269        &self.0
270    }
271}
272
273impl DerefMut for TxtLine {
274    fn deref_mut(&mut self) -> &mut Self::Target {
275        &mut self.0
276    }
277}
278
279impl Node for TxtLine {
280    fn serialize(&self) -> String {
281        self.0
282            .iter()
283            .map(|node| node.serialize())
284            .collect::<Vec<_>>()
285            .join("")
286    }
287}
288
289impl TxtLine {
290    /// Converts the line to a xml-like string representation.
291    pub fn to_xml(&self) -> String {
292        self.0
293            .iter()
294            .map(|node| node.to_xml())
295            .collect::<Vec<_>>()
296            .join("")
297    }
298}
299
300#[derive(Debug, Clone, PartialEq)]
301pub struct LineTag {
302    pub name: String,
303    pub list: Vec<String>,
304}
305
306impl Node for LineTag {
307    fn serialize(&self) -> String {
308        let list = self.list.join(",");
309        format!("#{} {}", self.name, list)
310    }
311}
312
313#[derive(Debug, Clone, PartialEq)]
314/// Represents a parsed line in Artemis TXT scripts.
315pub enum ParsedLine {
316    /// Empty line.
317    Empty(EmptyLineNode),
318    /// Comment line.
319    Comment(CommentNode),
320    /// Label line.
321    Label(LabelNode),
322    /// Line
323    Line(TxtLine),
324    /// Line tag
325    LineTag(LineTag),
326    /// Comment Line starts with ;>>
327    Comment2(CommentNode),
328}
329
330impl Node for ParsedLine {
331    fn serialize(&self) -> String {
332        match self {
333            ParsedLine::Empty(node) => node.serialize(),
334            ParsedLine::Comment(node) => node.serialize(),
335            ParsedLine::Label(node) => node.serialize(),
336            ParsedLine::Line(line) => line.serialize(),
337            ParsedLine::LineTag(node) => node.serialize(),
338            ParsedLine::Comment2(node) => format!(";>>{}", node.0),
339        }
340    }
341}
342
343impl ParsedLine {
344    /// Returns the length of the line.
345    pub fn len(&self) -> usize {
346        match self {
347            ParsedLine::Empty(_) => 0,
348            ParsedLine::Comment(_) => 0,
349            ParsedLine::Label(_) => 0,
350            ParsedLine::Line(line) => line.len(),
351            ParsedLine::LineTag(_) => 0,
352            ParsedLine::Comment2(_) => 0,
353        }
354    }
355
356    /// Push a node to the line.
357    pub fn push(&mut self, node: TxtLineNode) {
358        if let ParsedLine::Line(line) = self {
359            line.push(node);
360        } else {
361            // Do'nt care about other types
362        }
363    }
364
365    /// Inserts a node at the specified index in the line.
366    pub fn insert(&mut self, index: usize, node: TxtLineNode) {
367        if let ParsedLine::Line(line) = self {
368            line.insert(index, node);
369        } else {
370            // Do'nt care about other types
371        }
372    }
373
374    /// Remove a node at the specified index from the line.
375    pub fn remove(&mut self, index: usize) -> Option<TxtLineNode> {
376        if let ParsedLine::Line(line) = self {
377            if index < line.len() {
378                Some(line.remove(index))
379            } else {
380                None
381            }
382        } else {
383            // Do'nt care about other types
384            None
385        }
386    }
387}
388
389#[derive(Debug, Clone)]
390/// Represents a parsed Artemis TXT script.
391pub struct ParsedScript(pub Vec<ParsedLine>);
392
393impl Deref for ParsedScript {
394    type Target = Vec<ParsedLine>;
395
396    fn deref(&self) -> &Self::Target {
397        &self.0
398    }
399}
400
401impl DerefMut for ParsedScript {
402    fn deref_mut(&mut self) -> &mut Self::Target {
403        &mut self.0
404    }
405}
406
407impl Node for ParsedScript {
408    fn serialize(&self) -> String {
409        self.0
410            .iter()
411            .map(|line| line.serialize())
412            .collect::<Vec<_>>()
413            .join("\n")
414    }
415}
416
417/// Parser for Artemis TXT scripts.
418pub struct Parser {
419    lines: Vec<String>,
420}
421
422impl Parser {
423    pub fn new<S: AsRef<str> + ?Sized>(script: &S) -> Self {
424        let lines = script.as_ref().lines().map(|s| s.to_string()).collect();
425        Self { lines }
426    }
427
428    pub fn parse(&self, preserve_empty_lines: bool) -> Result<ParsedScript> {
429        let mut parsed_script = Vec::new();
430        let mut i = 0;
431        let line_count = self.lines.len();
432        while i < line_count {
433            let line = self.lines[i].trim();
434            i += 1;
435            if line.is_empty() {
436                if preserve_empty_lines {
437                    parsed_script.push(ParsedLine::Empty(EmptyLineNode));
438                }
439                continue;
440            }
441            if line.starts_with("//") {
442                parsed_script.push(ParsedLine::Comment(CommentNode(line[2..].to_string())));
443                continue;
444            }
445            if line.starts_with("*") {
446                let label = line[1..].trim().to_string();
447                parsed_script.push(ParsedLine::Label(LabelNode(label)));
448                continue;
449            }
450            if line.starts_with("#") {
451                let rest = line[1..].trim();
452                let mut parts = rest.splitn(2, ' ');
453                let name = parts
454                    .next()
455                    .ok_or(anyhow::anyhow!("Invalid line tag: {}", line))?
456                    .to_string();
457                let list = if let Some(list_str) = parts.next() {
458                    list_str
459                        .split(',')
460                        .map(|s| s.trim().to_string())
461                        .collect::<Vec<_>>()
462                } else {
463                    Vec::new()
464                };
465                parsed_script.push(ParsedLine::LineTag(LineTag { name, list }));
466                continue;
467            }
468            if line.starts_with(";>>") {
469                parsed_script.push(ParsedLine::Comment2(CommentNode(line[3..].to_string())));
470                continue;
471            }
472            let mut temp = String::new();
473            let mut nodes = Vec::new();
474            let mut line_graphs = line.graphemes(true).collect::<Vec<_>>();
475            let mut line_pos = 0;
476            let mut is_comment = false;
477            while line_pos < line_graphs.len() {
478                let graph = line_graphs[line_pos];
479                line_pos += 1;
480                temp.push_str(graph);
481                if is_comment {
482                    continue;
483                }
484                if !is_comment && temp.ends_with("//") && temp.len() > 2 {
485                    nodes.push(TxtLineNode::Text(TextNode(
486                        temp[..temp.len() - 2].to_string(),
487                    )));
488                    temp.clear();
489                    is_comment = true;
490                    continue;
491                }
492                if graph == "[" {
493                    if !temp.trim_end_matches("[").is_empty() {
494                        nodes.push(TxtLineNode::Text(TextNode(
495                            temp.trim_end_matches("[").to_string(),
496                        )));
497                    }
498                    // Tag may end in another line, so we need check it.
499                    while !line_graphs[line_pos..].contains(&"]") {
500                        if i < line_count {
501                            let nline = self.lines[i].trim();
502                            i += 1;
503                            // Add next line to the current line
504                            line_graphs.push("\n");
505                            line_graphs.extend(nline.graphemes(true));
506                        } else {
507                            break;
508                        }
509                    }
510                    let (tag, nextpos) = TagParser {
511                        graphs: &line_graphs,
512                        pos: line_pos,
513                    }
514                    .parse()?;
515                    line_pos = nextpos;
516                    nodes.push(TxtLineNode::Tag(tag));
517                    temp.clear();
518                    continue;
519                }
520            }
521            if is_comment {
522                nodes.push(TxtLineNode::Comment(CommentNode(temp)));
523            } else {
524                if !temp.is_empty() {
525                    nodes.push(TxtLineNode::Text(TextNode(temp)));
526                }
527            }
528            parsed_script.push(ParsedLine::Line(TxtLine(nodes)));
529        }
530        Ok(ParsedScript(parsed_script))
531    }
532}
533
534struct TagParser<'a> {
535    graphs: &'a [&'a str],
536    pos: usize,
537}
538
539impl<'a> TagParser<'a> {
540    fn peek(&self) -> Option<&'a str> {
541        self.graphs.get(self.pos).cloned()
542    }
543
544    fn eat(&mut self) {
545        if self.pos < self.graphs.len() {
546            self.pos += 1;
547        }
548    }
549
550    fn next(&mut self) -> Option<&'a str> {
551        if self.pos < self.graphs.len() {
552            let graph = self.graphs[self.pos];
553            self.pos += 1;
554            Some(graph)
555        } else {
556            None
557        }
558    }
559
560    fn is_indent(&self, indent: &str) -> bool {
561        let mut pos = self.pos;
562        for ident in indent.graphemes(true) {
563            if pos >= self.graphs.len() || self.graphs[pos] != ident {
564                return false;
565            }
566            pos += 1;
567        }
568        true
569    }
570
571    fn eat_all_equal(&mut self) {
572        while let Some(graph) = self.peek() {
573            if graph == "=" {
574                self.eat();
575            } else {
576                break;
577            }
578        }
579    }
580
581    fn parse(&mut self) -> Result<(TagNode, usize)> {
582        let name = self.parse_tag()?;
583        self.erase_whitespace();
584        let mut attributes = Vec::new();
585        loop {
586            let graph = match self.peek() {
587                Some(g) => g,
588                None => {
589                    return Err(anyhow::anyhow!(
590                        "Unexpected end of tag parsing: {}",
591                        self.graphs.join("")
592                    ));
593                }
594            };
595            if graph == "]" {
596                self.eat();
597                break;
598            }
599            if graph == " " || graph == "\t" {
600                self.eat();
601                continue;
602            }
603            if graph == "=" {
604                return Err(anyhow::anyhow!("Unexpected '=' without attribute name"));
605            }
606            let attr_name = self.parse_attr_name()?;
607            self.erase_whitespace();
608            let graph = match self.peek() {
609                Some(g) => g,
610                None => {
611                    return Err(anyhow::anyhow!(
612                        "Unexpected end of tag parsing: {}",
613                        self.graphs.join("")
614                    ));
615                }
616            };
617            if graph == "]" {
618                self.eat();
619                attributes.push((attr_name, None));
620                break;
621            }
622            if graph == "=" {
623                // Sometimes the script contains multiple equal signs
624                // We just ignore them
625                // Example: [イベントCG st = "拡大/ev005/a" add = "拡大/ev005/y2,拡大/ev005/r5" left = "min ~ max" top = "max ~ 1/4" mtime = "60000" ease == "減速" mfade = "1000" hide = "1"]
626                self.eat_all_equal();
627                self.erase_whitespace();
628                let value = self.parse_attr_value()?;
629                attributes.push((attr_name, Some(value)));
630                self.erase_whitespace();
631            } else {
632                attributes.push((attr_name, None));
633                self.erase_whitespace();
634                continue;
635            }
636        }
637        return Ok((TagNode { name, attributes }, self.pos));
638    }
639
640    fn erase_whitespace(&mut self) {
641        while let Some(graph) = self.peek() {
642            if graph == " " || graph == "\t" {
643                self.eat();
644            } else {
645                break;
646            }
647        }
648    }
649
650    fn parse_attr_name(&mut self) -> Result<String> {
651        let mut attr_name = String::new();
652        while let Some(graph) = self.peek() {
653            if graph == "=" || graph == " " || graph == "\t" || graph == "]" {
654                break;
655            }
656            attr_name.push_str(graph);
657            self.eat();
658        }
659        if attr_name.is_empty() {
660            return Err(anyhow::anyhow!("Empty attribute name found"));
661        }
662        Ok(attr_name)
663    }
664
665    fn parse_attr_value(&mut self) -> Result<String> {
666        let mut value = String::new();
667        if !self.is_indent("\"") {
668            return Err(anyhow::anyhow!(
669                "Expected attribute value to start with a quote: {}",
670                self.graphs.join("")
671            ));
672        }
673        self.eat(); // Skip the opening quote
674        while let Some(graph) = self.next() {
675            if graph == "\"" {
676                break; // End of attribute value
677            }
678            value.push_str(graph);
679        }
680        Ok(value)
681    }
682
683    fn parse_tag(&mut self) -> Result<String> {
684        let mut tag = String::new();
685        while let Some(graph) = self.peek() {
686            if graph == " " || graph == "\t" || graph == "]" {
687                break;
688            }
689            tag.push_str(graph);
690            self.eat();
691        }
692        if tag.is_empty() {
693            return Err(anyhow::anyhow!("Empty tag found"));
694        }
695        Ok(tag)
696    }
697}
698
699struct XMLTextParser<'a> {
700    str: &'a str,
701    lang: &'a str,
702    pos: usize,
703    no_rt2: bool,
704}
705
706impl<'a> XMLTextParser<'a> {
707    pub fn new(text: &'a str, lang: &'a str, no_rt2: bool) -> Self {
708        Self {
709            str: text,
710            lang,
711            pos: 0,
712            no_rt2,
713        }
714    }
715
716    fn parse_tag(&mut self) -> Result<TagNode> {
717        let mut name = String::new();
718        let mut attributes = Vec::new();
719        let mut is_name = true;
720        let mut is_key = false;
721        let mut is_value = false;
722        let mut is_in_quote = false;
723        let mut key = String::new();
724        let mut value = String::new();
725        while let Some(c) = self.next() {
726            match c {
727                '>' => {
728                    if !name.is_empty() {
729                        return Ok(TagNode { name, attributes });
730                    } else {
731                        return Err(anyhow::anyhow!("Empty tag name"));
732                    }
733                }
734                ' ' | '\t' => {
735                    if is_name {
736                        is_name = false;
737                        is_key = true;
738                    } else if is_key {
739                        if !key.is_empty() {
740                            attributes.push((key.clone(), None));
741                            key.clear();
742                        }
743                    } else if is_value {
744                        if is_in_quote {
745                            value.push(c);
746                        } else {
747                            if !value.is_empty() {
748                                attributes.push((key.clone(), Some(unescape_xml(&value))));
749                                key.clear();
750                                value.clear();
751                            }
752                            is_key = true;
753                            is_value = false;
754                        }
755                    }
756                }
757                '"' => {
758                    if is_in_quote {
759                        is_in_quote = false;
760                        if !value.is_empty() {
761                            attributes.push((key.clone(), Some(unescape_xml(&value))));
762                            key.clear();
763                            value.clear();
764                        }
765                        is_key = true;
766                    } else {
767                        is_in_quote = true;
768                    }
769                }
770                '=' => {
771                    if is_key {
772                        is_key = false;
773                        is_value = true;
774                    }
775                }
776                _ => {
777                    if is_name {
778                        name.push(c);
779                    } else if is_key {
780                        key.push(c);
781                    } else if is_value {
782                        value.push(c);
783                    } else {
784                        return Err(anyhow::anyhow!("Unexpected character in tag: {}", c));
785                    }
786                }
787            }
788        }
789        Err(anyhow::anyhow!("Unexpected end of input while parsing tag"))
790    }
791
792    pub fn parse(mut self) -> Result<Vec<ParsedLine>> {
793        let mut lines = Vec::new();
794        let mut current_line = Vec::new();
795        let mut text = String::new();
796        if !self.no_rt2 {
797            current_line.push(TxtLineNode::Tag(TagNode {
798                name: "lang".to_string(),
799                attributes: vec![(self.lang.to_string(), None)],
800            }));
801        }
802        while let Some(c) = self.next() {
803            match c {
804                '<' => {
805                    if !text.is_empty() {
806                        current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
807                        text.clear();
808                    }
809                    let tag = self.parse_tag()?;
810                    let is_r = tag.name == "rt2" || tag.name == "ret2";
811                    current_line.push(TxtLineNode::Tag(tag));
812                    if is_r {
813                        lines.push(ParsedLine::Line(TxtLine(current_line)));
814                        current_line = Vec::new();
815                    }
816                }
817                '\n' => {
818                    if !text.is_empty() {
819                        current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
820                        text.clear();
821                    }
822                    if !self.no_rt2 {
823                        current_line.push(TxtLineNode::Tag(TagNode {
824                            name: "rt2".to_string(),
825                            attributes: Vec::new(),
826                        }));
827                    }
828                    lines.push(ParsedLine::Line(TxtLine(current_line)));
829                    current_line = Vec::new();
830                }
831                _ => text.push(c),
832            }
833        }
834        if !text.is_empty() {
835            current_line.push(TxtLineNode::Text(TextNode(unescape_xml(&text))));
836        }
837        if !self.no_rt2 {
838            current_line.push(TxtLineNode::Tag(TagNode {
839                name: "/lang".to_string(),
840                attributes: Vec::new(),
841            }));
842        }
843        lines.push(ParsedLine::Line(TxtLine(current_line)));
844        Ok(lines)
845    }
846
847    fn next(&mut self) -> Option<char> {
848        if self.pos < self.str.len() {
849            let c = self.str[self.pos..].chars().next()?;
850            self.pos += c.len_utf8();
851            Some(c)
852        } else {
853            None
854        }
855    }
856}
857
858#[derive(Debug)]
859pub struct TxtScript {
860    tree: ParsedScript,
861    blacklist_names: Arc<HashSet<String>>,
862    lang: Option<String>,
863    multi_lang: bool,
864}
865
866impl TxtScript {
867    /// Creates a new instance of `TxtScript` from the given buffer and encoding.
868    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
869        let script = decode_to_string(encoding, &buf, true)?;
870        let parser = Parser::new(&script);
871        let tree = parser.parse(true)?;
872        Ok(Self {
873            tree,
874            blacklist_names: config.artemis_panmimisoft_txt_blacklist_names.clone(),
875            lang: config.artemis_panmimisoft_txt_lang.clone(),
876            multi_lang: config.artemis_panmimisoft_txt_multi_lang,
877        })
878    }
879}
880
881impl Script for TxtScript {
882    fn default_output_script_type(&self) -> OutputScriptType {
883        OutputScriptType::Json
884    }
885
886    fn default_format_type(&self) -> FormatOptions {
887        FormatOptions::None
888    }
889
890    fn extract_messages(&self) -> Result<Vec<Message>> {
891        let mut messages = Vec::new();
892        let mut i = 0;
893        let len = self.tree.len();
894        let mut last_tag_block: Option<TagNode> = None;
895        let mut lang = self.lang.as_ref().map(|s| s.as_str());
896        let mut message = TxtLine(Vec::new());
897        let mut in_lang_block = false;
898        let mut droped_lang_block = false;
899        let mut is_selectblk = false;
900        let mut has_printlang = false;
901        for line in &self.tree.0 {
902            if let ParsedLine::Line(txt_line) = line {
903                for node in txt_line.iter() {
904                    if node.is_tag("printlang") {
905                        has_printlang = true;
906                        break;
907                    }
908                }
909            }
910        }
911        if !has_printlang {
912            let mut message_started = false;
913            let mut adv_started = false;
914            while i < len {
915                let line = &self.tree[i];
916                match line {
917                    ParsedLine::Empty(_) => {
918                        let mes = message.to_xml();
919                        message.clear();
920                        message_started = false;
921                        if !mes.is_empty() && adv_started {
922                            let name = match &last_tag_block {
923                                Some(block) => Some(if let Some(name) = block.get_attr("name") {
924                                    name.to_string()
925                                } else {
926                                    block.name.clone()
927                                }),
928                                _ => None,
929                            };
930                            messages.push(Message { name, message: mes });
931                        }
932                        last_tag_block = None;
933                    }
934                    ParsedLine::Line(line) => {
935                        if !message.is_empty() && message_started {
936                            message.push(TxtLineNode::Tag(TagNode {
937                                name: "rt2".into(),
938                                attributes: vec![],
939                            }));
940                        }
941                        for node in line.iter() {
942                            if node.is_tag("adv") {
943                                adv_started = true;
944                            } else if node.is_tag("selectbtn_init") {
945                                is_selectblk = true;
946                            } else if node.is_tag("selectbtn") {
947                                let text = node.tag_get_attr("text").ok_or(anyhow::anyhow!(
948                                    "No text attribute found in selectbtn tag"
949                                ))?;
950                                messages.push(Message {
951                                    name: None,
952                                    message: text.to_string(),
953                                });
954                            } else if node.is_tag("/selectbtn") {
955                                is_selectblk = false;
956                            } else if let TxtLineNode::Tag(tag) = node {
957                                if !message_started {
958                                    if !tag.is_blocked_name(&self.blacklist_names) {
959                                        last_tag_block = Some(tag.clone());
960                                    }
961                                } else {
962                                    message.push(node.clone());
963                                }
964                            } else if node.is_comment() {
965                                // Ignore comments
966                            } else {
967                                message_started = true;
968                                message.push(node.clone());
969                            }
970                        }
971                    }
972                    _ => {}
973                }
974                i += 1;
975            }
976        }
977        while i < len {
978            let line = &self.tree[i];
979            if let ParsedLine::Line(line) = line {
980                for node in line.iter() {
981                    if node.is_tag("lang") {
982                        let lan = match lang {
983                            Some(l) => l,
984                            None => {
985                                for key in node.tag_attr_keys() {
986                                    lang = Some(key);
987                                    break;
988                                }
989                                match lang {
990                                    Some(l) => l,
991                                    None => {
992                                        return Err(anyhow::anyhow!(
993                                            "No language found in lang tag"
994                                        ));
995                                    }
996                                }
997                            }
998                        };
999                        if node.tag_has_attr(lan) {
1000                            in_lang_block = true;
1001                        } else {
1002                            droped_lang_block = true;
1003                        }
1004                    } else if node.is_tag("/lang") {
1005                        in_lang_block = false;
1006                        droped_lang_block = false;
1007                    } else if node.is_tag("printlang") {
1008                        let mes = message.to_xml();
1009                        message.clear();
1010                        if !mes.is_empty() {
1011                            let name = match &last_tag_block {
1012                                Some(block) => Some(if let Some(name) = block.get_attr("name") {
1013                                    name.to_string()
1014                                } else {
1015                                    block.name.clone()
1016                                }),
1017                                _ => None,
1018                            };
1019                            messages.push(Message { name, message: mes });
1020                        }
1021                        last_tag_block = None;
1022                    } else if node.is_tag("selectbtn_init") {
1023                        is_selectblk = true;
1024                    } else if node.is_tag("selectbtn") {
1025                        let mut lan = match lang {
1026                            Some(l) => l,
1027                            None => {
1028                                for key in node.tag_attr_keys() {
1029                                    if key == "label" || key == "call" {
1030                                        continue;
1031                                    }
1032                                    lang = Some(key);
1033                                    break;
1034                                }
1035                                match lang {
1036                                    Some(l) => l,
1037                                    None => {
1038                                        return Err(anyhow::anyhow!(
1039                                            "No language found in selectbtn tag"
1040                                        ));
1041                                    }
1042                                }
1043                            }
1044                        };
1045                        if !node.tag_has_attr(lan) {
1046                            for key in node.tag_attr_keys() {
1047                                if key == "label" || key == "call" {
1048                                    continue;
1049                                }
1050                                lan = key;
1051                                break;
1052                            }
1053                        }
1054                        if let Some(t) = node.tag_get_attr(lan) {
1055                            messages.push(Message {
1056                                name: None,
1057                                message: t.to_string(),
1058                            });
1059                        }
1060                    } else if node.is_tag("/selectbtn") {
1061                        is_selectblk = false;
1062                    } else if in_lang_block {
1063                        message.push(node.clone());
1064                    } else if droped_lang_block {
1065                        // Drop the message if we are in a dropped lang block
1066                    } else if is_selectblk {
1067                        // Drop other nodes in select block
1068                    } else if let TxtLineNode::Tag(tag) = node {
1069                        if !tag.is_blocked_name(&self.blacklist_names) {
1070                            last_tag_block = Some(tag.clone());
1071                        }
1072                    }
1073                }
1074            }
1075            i += 1;
1076        }
1077        Ok(messages)
1078    }
1079
1080    fn import_messages<'a>(
1081        &'a self,
1082        messages: Vec<Message>,
1083        mut file: Box<dyn WriteSeek + 'a>,
1084        _filename: &str,
1085        encoding: Encoding,
1086        replacement: Option<&'a ReplacementTable>,
1087    ) -> Result<()> {
1088        let mut output = self.tree.clone();
1089        let mut current_line = 0;
1090        let mut last_tag_block_loc = None;
1091        let mut lang = self.lang.clone();
1092        let mut mes = messages.iter();
1093        let mut mess = mes.next();
1094        let mut lang_block_index = None;
1095        let mut lang_end_block_index = None;
1096        let mut in_lang_block = false;
1097        let mut droped_lang_block = false;
1098        let mut is_selectblk = false;
1099        let mut has_printlang = false;
1100        for line in &output.0 {
1101            if let ParsedLine::Line(txt_line) = line {
1102                for node in txt_line.iter() {
1103                    if node.is_tag("printlang") {
1104                        has_printlang = true;
1105                        break;
1106                    }
1107                }
1108            }
1109        }
1110        if !has_printlang {
1111            let mut adv_started = false;
1112            let mut started_line: Option<usize> = None;
1113            if self.multi_lang && lang.is_none() {
1114                return Err(anyhow::anyhow!(
1115                    "Multi-language import requires a specified language. Use --artemis-panmimisoft-txt-lang to set the language."
1116                ));
1117            } else if !self.multi_lang {
1118                lang = None;
1119            }
1120            while current_line < output.len() {
1121                let line = output[current_line].clone();
1122                match &line {
1123                    ParsedLine::Empty(_) => {
1124                        if adv_started {
1125                            if let Some(start) = started_line {
1126                                let m = match mess {
1127                                    Some(m) => m,
1128                                    None => {
1129                                        return Err(anyhow::anyhow!("Not enough messages."));
1130                                    }
1131                                };
1132                                let mut message = m.message.clone();
1133                                if let Some(repl) = replacement {
1134                                    for (k, v) in &repl.map {
1135                                        message = message.replace(k, v);
1136                                    }
1137                                }
1138                                if let Some(name) = &m.name {
1139                                    let block_index: (usize, usize) =
1140                                        match last_tag_block_loc.take() {
1141                                            Some(data) => data,
1142                                            None => {
1143                                                return Err(anyhow::anyhow!(
1144                                                    "No name tag block found before message."
1145                                                ));
1146                                            }
1147                                        };
1148                                    let mut name = name.clone();
1149                                    if let Some(repl) = replacement {
1150                                        for (k, v) in &repl.map {
1151                                            name = name.replace(k, v);
1152                                        }
1153                                    }
1154                                    let mblock = &mut output[block_index.0];
1155                                    if let ParsedLine::Line(txt_line) = mblock {
1156                                        let block = txt_line[block_index.1].clone();
1157                                        if let TxtLineNode::Tag(mut tag) = block {
1158                                            tag.set_attr("name", Some(name));
1159                                            txt_line[block_index.1] = TxtLineNode::Tag(tag);
1160                                        } else {
1161                                            return Err(anyhow::anyhow!(
1162                                                "Last tag block is not a tag: {:?}",
1163                                                mblock
1164                                            ));
1165                                        }
1166                                    } else {
1167                                        return Err(anyhow::anyhow!(
1168                                            "Last tag block is not a line: {:?}",
1169                                            mblock
1170                                        ));
1171                                    }
1172                                }
1173                                let mut nodes = XMLTextParser::new(
1174                                    &message,
1175                                    lang.as_ref().map(|s| s.as_str()).unwrap_or(""),
1176                                    !self.multi_lang,
1177                                )
1178                                .parse()?;
1179                                if self.multi_lang {
1180                                    // Add a printlang tag
1181                                    nodes.push(ParsedLine::Line(TxtLine(vec![TxtLineNode::Tag(
1182                                        TagNode {
1183                                            name: "printlang".to_string(),
1184                                            attributes: Vec::new(),
1185                                        },
1186                                    )])));
1187                                }
1188                                let ori_len = (current_line - start) as isize;
1189                                let new_len = nodes.len() as isize;
1190                                for _ in start..current_line {
1191                                    output.remove(start);
1192                                }
1193                                let mut start_index = start;
1194                                for node in nodes {
1195                                    output.insert(start_index, node);
1196                                    start_index += 1;
1197                                }
1198                                current_line = (current_line as isize + new_len - ori_len) as usize;
1199                                mess = mes.next();
1200                            }
1201                        }
1202                        started_line = None;
1203                        last_tag_block_loc = None;
1204                    }
1205                    ParsedLine::Line(line) => {
1206                        for (i, node) in line.iter().enumerate() {
1207                            if node.is_tag("adv") {
1208                                adv_started = true;
1209                            } else if node.is_tag("selectbtn_init") {
1210                                is_selectblk = true;
1211                            } else if node.is_tag("selectbtn") {
1212                                let m = match mess {
1213                                    Some(m) => m,
1214                                    None => {
1215                                        return Err(anyhow::anyhow!("Not enough messages."));
1216                                    }
1217                                };
1218                                let mut message = m.message.clone();
1219                                if let Some(repl) = replacement {
1220                                    for (k, v) in &repl.map {
1221                                        message = message.replace(k, v);
1222                                    }
1223                                }
1224                                let mut node = node.clone();
1225                                node.tag_set_attr(
1226                                    lang.as_ref().map(|s| s.as_str()).unwrap_or("text"),
1227                                    Some(message),
1228                                );
1229                                let block = &mut output[current_line];
1230                                if let ParsedLine::Line(txt_line) = block {
1231                                    txt_line[i] = node;
1232                                } else {
1233                                    return Err(anyhow::anyhow!(
1234                                        "Selectbtn line is not a line: {:?}",
1235                                        block
1236                                    ));
1237                                }
1238                                mess = mes.next();
1239                            } else if let TxtLineNode::Tag(tag) = node {
1240                                if started_line.is_none() {
1241                                    if !tag.is_blocked_name(&self.blacklist_names) {
1242                                        last_tag_block_loc = Some((current_line, i));
1243                                    }
1244                                }
1245                            } else if node.is_comment() {
1246                                // Ignore comments
1247                            } else {
1248                                if started_line.is_none() {
1249                                    started_line = Some(current_line);
1250                                }
1251                            }
1252                        }
1253                    }
1254                    _ => {}
1255                }
1256                current_line += 1;
1257            }
1258        }
1259        while current_line < output.len() {
1260            let line = output[current_line].clone();
1261            if let ParsedLine::Line(line) = &line {
1262                for (i, node) in line.iter().enumerate() {
1263                    if node.is_tag("lang") {
1264                        let lan = match lang.as_ref() {
1265                            Some(l) => l.as_str(),
1266                            None => {
1267                                for key in node.tag_attr_keys() {
1268                                    lang = Some(key.to_string());
1269                                    break;
1270                                }
1271                                match lang.as_ref() {
1272                                    Some(l) => l.as_str(),
1273                                    None => {
1274                                        return Err(anyhow::anyhow!(
1275                                            "No language found in lang tag"
1276                                        ));
1277                                    }
1278                                }
1279                            }
1280                        };
1281                        if node.tag_has_attr(lan) {
1282                            in_lang_block = true;
1283                            lang_block_index = Some((current_line, i));
1284                        } else {
1285                            droped_lang_block = true;
1286                        }
1287                    } else if node.is_tag("/lang") {
1288                        if in_lang_block {
1289                            lang_end_block_index = Some((current_line, i));
1290                        }
1291                        in_lang_block = false;
1292                        droped_lang_block = false;
1293                    } else if node.is_tag("printlang") {
1294                        let lan = lang
1295                            .as_ref()
1296                            .map(|s| s.as_str())
1297                            .ok_or(anyhow::anyhow!("No language specified."))?;
1298                        let m = match mess {
1299                            Some(m) => m,
1300                            None => {
1301                                return Err(anyhow::anyhow!("Not enough messages."));
1302                            }
1303                        };
1304                        if let Some(name) = &m.name {
1305                            let block_index: (usize, usize) = match last_tag_block_loc.take() {
1306                                Some(data) => data,
1307                                None => {
1308                                    return Err(anyhow::anyhow!(
1309                                        "No name tag block found before printlang.",
1310                                    ));
1311                                }
1312                            };
1313                            let mut name = name.clone();
1314                            if let Some(repl) = replacement {
1315                                for (k, v) in &repl.map {
1316                                    name = name.replace(k, v);
1317                                }
1318                            }
1319                            let mblock = &mut output[block_index.0];
1320                            if let ParsedLine::Line(txt_line) = mblock {
1321                                let block = txt_line[block_index.1].clone();
1322                                if let TxtLineNode::Tag(mut tag) = block {
1323                                    tag.set_attr("name", Some(name));
1324                                    txt_line[block_index.1] = TxtLineNode::Tag(tag);
1325                                } else {
1326                                    return Err(anyhow::anyhow!(
1327                                        "Last tag block is not a tag: {:?}",
1328                                        mblock
1329                                    ));
1330                                }
1331                            } else {
1332                                return Err(anyhow::anyhow!(
1333                                    "Last tag block is not a line: {:?}",
1334                                    mblock
1335                                ));
1336                            }
1337                        }
1338                        let mut message = m.message.clone();
1339                        if let Some(repl) = replacement {
1340                            for (k, v) in &repl.map {
1341                                message = message.replace(k, v);
1342                            }
1343                        }
1344                        let mut nodes = XMLTextParser::new(&message, lan, false).parse()?;
1345                        if lang_block_index.is_some() && lang_end_block_index.is_some() {
1346                            let start_index = lang_block_index.unwrap();
1347                            let end_index = lang_end_block_index.unwrap();
1348                            if start_index.1 != 0 {
1349                                let block = output[start_index.0].clone();
1350                                if let ParsedLine::Line(txt_line) = block {
1351                                    for i in 0..start_index.1 {
1352                                        nodes[0].insert(i, txt_line[i].clone());
1353                                    }
1354                                } else {
1355                                    return Err(anyhow::anyhow!(
1356                                        "Lang block start is not a line: {:?}",
1357                                        block
1358                                    ));
1359                                }
1360                            }
1361                            if end_index.1 + 1 < output[end_index.0].len() {
1362                                let block = output[end_index.0].clone();
1363                                if let ParsedLine::Line(txt_line) = block {
1364                                    for i in end_index.1 + 1..txt_line.len() {
1365                                        nodes.last_mut().unwrap().push(txt_line[i].clone());
1366                                    }
1367                                } else {
1368                                    return Err(anyhow::anyhow!(
1369                                        "Lang block end is not a line: {:?}",
1370                                        block
1371                                    ));
1372                                }
1373                            }
1374                            let ori_len = (end_index.0 - start_index.0 + 1) as isize;
1375                            let new_len = nodes.len() as isize;
1376                            for _ in start_index.0..=end_index.0 {
1377                                output.remove(start_index.0);
1378                            }
1379                            let mut start_index = start_index.0;
1380                            for node in nodes {
1381                                output.insert(start_index, node);
1382                                start_index += 1;
1383                            }
1384                            current_line = (current_line as isize + new_len - ori_len) as usize;
1385                        } else {
1386                            // Add a new lang block if not exists
1387                            for node in nodes {
1388                                output.insert(current_line, node);
1389                                current_line += 1;
1390                            }
1391                        }
1392                        lang_block_index = None;
1393                        lang_end_block_index = None;
1394                        mess = mes.next();
1395                        last_tag_block_loc = None;
1396                    } else if node.is_tag("selectbtn_init") {
1397                        is_selectblk = true;
1398                    } else if node.is_tag("selectbtn") {
1399                        let lan = match lang.as_ref() {
1400                            Some(l) => l.as_str(),
1401                            None => {
1402                                for key in node.tag_attr_keys() {
1403                                    if key == "label" || key == "call" {
1404                                        continue;
1405                                    }
1406                                    lang = Some(key.to_string());
1407                                    break;
1408                                }
1409                                match lang.as_ref() {
1410                                    Some(l) => l.as_str(),
1411                                    None => {
1412                                        return Err(anyhow::anyhow!(
1413                                            "No language found in selectbtn tag"
1414                                        ));
1415                                    }
1416                                }
1417                            }
1418                        };
1419                        let m = match mess {
1420                            Some(m) => m,
1421                            None => {
1422                                return Err(anyhow::anyhow!("Not enough messages."));
1423                            }
1424                        };
1425                        let mut message = m.message.clone();
1426                        if let Some(repl) = replacement {
1427                            for (k, v) in &repl.map {
1428                                message = message.replace(k, v);
1429                            }
1430                        }
1431                        let mut node = node.clone();
1432                        node.tag_set_attr(lan, Some(message));
1433                        let block = &mut output[current_line];
1434                        if let ParsedLine::Line(txt_line) = block {
1435                            txt_line[i] = node;
1436                        } else {
1437                            return Err(anyhow::anyhow!("selectbtn tag not in line: {:?}", block));
1438                        }
1439                        mess = mes.next();
1440                    } else if node.is_tag("/selectbtn") {
1441                        is_selectblk = false;
1442                    } else if in_lang_block {
1443                        // Do nothing
1444                    } else if droped_lang_block {
1445                        // Drop the message if we are in a dropped lang block
1446                    } else if is_selectblk {
1447                        // Ignore other nodes in select block
1448                    } else if let TxtLineNode::Tag(tag) = node {
1449                        if !tag.is_blocked_name(&self.blacklist_names) {
1450                            last_tag_block_loc = Some((current_line, i));
1451                        }
1452                    }
1453                }
1454            }
1455            current_line += 1;
1456        }
1457        let s = output.serialize();
1458        let encoded = encode_string(encoding, &s, false)?;
1459        file.write_all(&encoded)?;
1460        file.flush()?;
1461        Ok(())
1462    }
1463}
1464
1465/// Reads tags list from tag.ini file.
1466pub fn read_tags_from_ini<P: AsRef<std::path::Path>>(path: P) -> Result<HashSet<String>> {
1467    let conf = ini::Ini::load_from_file(path)?;
1468    let set = HashSet::from_iter(conf.sections().flat_map(|s| s.map(|s| s.to_string())));
1469    eprintln!(
1470        "Read tags from ini: {}",
1471        set.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(",")
1472    );
1473    Ok(set)
1474}
1475
1476#[test]
1477fn test_xml_parser() {
1478    let data = "测试文本\nok<r a=\"b\">测试<b o=\"文本\n换行\">";
1479    let data = XMLTextParser::new(data, "en", false).parse().unwrap();
1480    assert_eq!(
1481        data,
1482        vec![
1483            ParsedLine::Line(TxtLine(vec![
1484                TxtLineNode::Tag(TagNode {
1485                    name: "lang".to_string(),
1486                    attributes: vec![("en".to_string(), None)],
1487                }),
1488                TxtLineNode::Text(TextNode("测试文本".to_string())),
1489                TxtLineNode::Tag(TagNode {
1490                    name: "rt2".to_string(),
1491                    attributes: vec![],
1492                }),
1493            ])),
1494            ParsedLine::Line(TxtLine(vec![
1495                TxtLineNode::Text(TextNode("ok".to_string())),
1496                TxtLineNode::Tag(TagNode {
1497                    name: "r".to_string(),
1498                    attributes: vec![("a".to_string(), Some("b".to_string()))],
1499                }),
1500                TxtLineNode::Text(TextNode("测试".to_string())),
1501                TxtLineNode::Tag(TagNode {
1502                    name: "b".to_string(),
1503                    attributes: vec![("o".to_string(), Some("文本\n换行".to_string()))],
1504                }),
1505                TxtLineNode::Tag(TagNode {
1506                    name: "/lang".to_string(),
1507                    attributes: vec![],
1508                }),
1509            ])),
1510        ],
1511    );
1512}